﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Affinity.Core.Validation;

namespace Affinity.Core.Utility
{
    public static class Expressions
    {
        public static void Compile<TExpressionDelegate>(Expression<TExpressionDelegate> expression, Action<TExpressionDelegate> callback)
        {
            if (expression == null) throw new ArgumentNullException("expression");
            if (callback == null) throw new ArgumentNullException("callback");

            callback(CompileSync<TExpressionDelegate>(expression));
        }

        public static TExpressionDelegate CompileSync<TExpressionDelegate>(Expression<TExpressionDelegate> expression)
        {
            TExpressionDelegate compilationResult;
//#if DEBUG
//            compilationResult = expression.Compile(_DebugInfoGenerator);
//#else
            compilationResult = expression.Compile();
//#endif
            return compilationResult;
        }

        public static void Compile(Expression expression, Action<Delegate> callback, params ParameterExpression[] parameters)
        {
            if (expression == null) throw new ArgumentNullException("expression");
            if (callback == null) throw new ArgumentNullException("callback");

            callback(CompileSync(expression));
        }

        public static Delegate CompileSync(Expression expression, params ParameterExpression[] parameters)
        {
            Delegate compilationResult;
            LambdaExpression lambdaExpression = expression as LambdaExpression ?? Expression.Lambda(expression, parameters);
//#if DEBUG
//            compilationResult = lambdaExpression.Compile(_DebugInfoGenerator);
//#else
            compilationResult = lambdaExpression.Compile();
//#endif
            return compilationResult;
        }

        public static PropertyInfo[] GetAllPropertyReferences(Expression expression)
        {
            Assert.NotNull(expression, "expression");
            return new PropertyVisitor().GetProperties(expression);
        }

        public static PropertyInfo GetSinglePropertyReference(Expression expression)
        {
            PropertyInfo[] allPropertyReferences = GetAllPropertyReferences(expression);
            Assert.IsTrue(allPropertyReferences.Length == 1, "Expression with one property reference is expected.");
            return allPropertyReferences[0];
        }

        public static PropertyInfo GetSinglePropertyReference<TSource, TResult>(Expression<Func<TSource, TResult>> expression)
        {
            return GetSinglePropertyReference((Expression)expression);
        }  

        public static PropertyInfo GetSinglePropertyReference<TSource>(Expression<Func<TSource, object>> expression)
        {
            return GetSinglePropertyReference((Expression)expression);
        } 

        private class PropertyVisitor : ExpressionVisitor
        {
            public PropertyInfo[] GetProperties(Expression expression)
            {
                _Properties.Clear();
                Visit(expression);
                return _Properties.ToArray();
            }

            protected override Expression VisitMember(MemberExpression node)
            {
                if (node.Member is PropertyInfo) _Properties.Add((PropertyInfo)node.Member);
                return base.VisitMember(node);
            }

            private List<PropertyInfo> _Properties = new List<PropertyInfo>();
        }

#if DEBUG
        //private static DebugInfoGenerator _DebugInfoGenerator = DebugInfoGenerator.CreatePdbGenerator();
#endif
    }
}
